﻿package com.model
{
	import com.ui.drag.IDragItem;

	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.Proxy;
	import flash.utils.flash_proxy;

	/**
	 * List Model
	 * 
	 * @author Cafe
	 * @version 20100802
	 */
	public class ListModel extends Proxy implements IEventDispatcher
	{
		private var _allowNull:Boolean;
		private var _fireEvent:Boolean;
		private var _max:int;
		private var _place:int;
		private var _source:Array;
		private var _oldSource:Array;
		private var _sortFun:Function;
		// 偏移量主要解决背包中起始位置不从1开始的问题
		private var _offset:int = 0;
		protected var _eventDispatcher:EventDispatcher;
		public var auto:Boolean = false;

		public function ListModel( allowNull:Boolean = false, max:int = 0, place:int = -1, offset:int = 0 )
		{
			_allowNull = allowNull;
			_max = max;
			_place = place;
			_fireEvent = true;
			_offset = offset;
			_source = new Array();
			_eventDispatcher = new EventDispatcher( this );
		}

		public function get offset():int
		{
			return _offset;
		}

		public function set allowNull( value:Boolean ):void
		{
			_allowNull = value;
		}

		public function get allowNull():Boolean
		{
			return _allowNull;
		}

		public function set fireEvent( value:Boolean ):void
		{
			_fireEvent = value;
		}

		public function set max( value:int ):void
		{
			if (value == 48 || _place == 3)
			{
			}
			value = Math.max( 0, value ) + _offset;
			if (_max == value) return;
			_max = value;
			if (size > _max) _source.splice( _max );
			dispatchEvent( new Event( Event.RESIZE ) );
		}

		public function get max():int
		{
			return _max - _offset;
		}

		public function set place( value:int ):void
		{
			_place = value;
			for (var i:int = 0; i < _source.length; i++)
			{
				if (_source[i]) _source[i].place = value;
			}
		}

		public function get place():int
		{
			return _place;
		}

		public function set source( value:Array ):void
		{
			_oldSource = _source.concat();
			if (value == null)
			{
				if (_source.length > 0)
				{
					_source.splice( 0 );
				}
			}
			else
			{
				_source = value;
				if (_max > 0 && size > _max)
				{
					_source.splice( _max );
				}
				if (_allowNull)
				{
					for (var i:int = 0;i < _source.length;i++)
					{
						var item:IDragItem = _source[i] as IDragItem;
						if (item != null)
						{
							item.place = _place;
							item.gird = i;
						}
					}
				}
			}
			dispatchEvent( new Event( Event.RESIZE ) );
			if (_fireEvent) dispatchEvent( new ListEvent( ListEvent.CHANGE, ListState.RESET ) );
		}

		public function get source():Array
		{
			return _source;
		}

		public function get oldSource():Array
		{
			return _oldSource;
		}

		public function set sortFun( value:Function ):void
		{
			_sortFun = value;
		}

		public function sort():void
		{
			if (_sortFun != null)
			{
				_source.sort( _sortFun );
				if (_fireEvent)
				{
					dispatchEvent( new ListEvent( ListEvent.CHANGE, ListState.RESET ) );
				}
			}
		}

		public function clear():void
		{
			_oldSource = _source.concat();
			_source.splice( 0 );
			if (_fireEvent) dispatchEvent( new ListEvent( ListEvent.CHANGE, ListState.RESET ) );
		}

		public function add( item:Object ):void
		{
			if (item == null) return;
			var index:int;
			if (_allowNull)
			{
				index = findFree();
				if (index == -1) return;
				setAt( index, item );
			}
			else
			{
				if (_max > 0 && _source.length >= _max) return;
				index = _source.push( item ) - 1;
				if (item is IDragItem)
				{
					item.place = _place;
					item.gird = index;
				}
				dispatchEvent( new Event( Event.RESIZE ) );
				if (_fireEvent) dispatchEvent( new ListEvent( ListEvent.CHANGE, ListState.ADDED, index, item ) );
			}
		}

		public function adds( items:Array ):void
		{
			for (var i:int = 0; i < items.length; i++)
			{
				add( items[i] );
			}
		}

		public function insert( index:int, item:Object ):void
		{
			if (index < 0 || index > size) return;
			if (_allowNull)
			{
				setAt( index, item );
			}
			else
			{
				if (_max > 0 && size >= _max) return;
				_source.splice( index, 0, item );
				dispatchEvent( new Event( Event.RESIZE ) );
				if (_fireEvent) dispatchEvent( new ListEvent( ListEvent.CHANGE, ListState.INSERT, index, item ) );
			}
		}

		public function remove( item:Object ):void
		{
			var index:int = _source.indexOf( item );
			if (index != -1) removeAt( index );
		}

		public function removeAt( index:int ):void
		{
			if (index < 0 || index >= size) return;
			if (_allowNull)
			{
				setAt( index, null );
			}
			else
			{
				var item:Object = _source.splice( index, 1 )[0];
				dispatchEvent( new Event( Event.RESIZE ) );
				if (_fireEvent) dispatchEvent( new ListEvent( ListEvent.CHANGE, ListState.REMOVED, index, null, item ) );
			}
		}

		public function setAt( index:int, item:Object ):void
		{
			if (_max > 0 && index >= _max)
			{
				return;
			}
			var oldItem:Object;
			if (_allowNull)
			{
				if (_source[index] == item && !(item is IDragItem) )
				{
					return;
				}
				oldItem = _source[index];
				_source[index] = item;
				if (item is IDragItem)
				{
					item.place = _place;
					item.gird = index;
				}
				if (_fireEvent)
				{
					dispatchEvent( new ListEvent( ListEvent.CHANGE, ListState.UPDATE, index, item, oldItem ) );
				}
			}
			else
			{
				if (index < 0 || index > _source.length)
				{
					return;
				}
				if (_source[index] == item) return;
				oldItem = _source[index];
				if (item is IDragItem)
				{
					item.place = _place;
					item.gird = index;
				}
				if (item != null)
				{
					_source[index] = item;
					if (_fireEvent)
					{
						dispatchEvent( new ListEvent( ListEvent.CHANGE, oldItem == null ? ListState.ADDED : ListState.UPDATE, index, item, oldItem ) );
					}
				}
				else
				{
					removeAt( index );
				}
			}
		}

		public function getAt( index:uint ):Object
		{
			return _source[index];
		}

		public function getLast():Object
		{
			return _source[size - 1];
		}

		public function indexOf( value:Object ):int
		{
			return _source.indexOf( value );
		}

		public function findAt( key:String, value:Object ):int
		{
			for (var index:int = 0;index < _source.length;index++)
			{
				var item:Object = _source[index];
				if (item != null && item.hasOwnProperty( key ) && item[key] == value)
				{
					return index;
				}
			}
			return -1;
		}

		public function findNOFreeIndex():int
		{
			for (var i:int = 0; i < _source.length; i++)
			{
				if (_source[i] != null) return i;
			}
			return -1;
		}

		public function findFree():int
		{
			if (_allowNull)
			{
				for (var index:int = 0;index < _source.length;index++)
				{
					if (_source[index + _offset] == null)
					{
						index = index + _offset;
						break;
					}
				}
				if (_source.length == 0) return 0 + _offset;
				if (_max > 0 && index >= _max)
				{
					return -1;
				}
				return index;
			}
			else
			{
				if (_max > 0)
				{
					if (_source.length < _max)
					{
						return _source.length;
					}
					else
					{
						return -1;
					}
				}
				else
				{
					return _source.length;
				}
			}
		}

		public function update( index:int = -1 ):void
		{
			if (index != -1)
			{
				if (isValid( index ))
				{
					if (_fireEvent)
					{
						var item:Object = getAt( index );
						dispatchEvent( new ListEvent( ListEvent.CHANGE, ListState.UPDATE, index, item, item ) );
					}
				}
			}
			else
			{
				if (_fireEvent)
				{
					dispatchEvent( new ListEvent( ListEvent.CHANGE, ListState.RESET ) );
				}
			}
		}

		public function isValid( index:int ):Boolean
		{
			if (index < 0) return false;
			if (_max > 0)
			{
				if (index >= max) return false;
			}
			else
			{
				if (index >= size) return false;
			}
			return true;
		}

		public function get size():int
		{
			return _source.length;
		}

		public function get validSize():int
		{
			if (_allowNull)
			{
				var size:int = 0;
				for (var index:int = 0;index < _source.length;index++)
				{
					if (_source[index] != null)
					{
						size++;
					}
				}
				return size;
			}
			else
			{
				return _source.length;
			}
		}

		public function get freeSize():int
		{
			if (_allowNull)
			{
				if (_max > 0)
				{
					return _max - validSize;
				}
				else
				{
					return 0;
				}
			}
			else
			{
				return 0;
			}
		}

		public function toArray():Array
		{
			return _source.concat();
		}

		public function toTrimArray():Array
		{
			if (_allowNull)
			{
				var list:Array = new Array();
				for (var index:int = 0;index < _source.length;index++)
				{
					if (_source[index] != null)
					{
						list.push( _source[index] );
					}
				}
				return list;
			}
			else
			{
				return _source.concat();
			}
		}

		public function get length():int
		{
			return _source.length;
		}

		public function set offset( offset:int ):void
		{
			_offset = offset;
		}

		public function addEventListener( type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false ):void
		{
			_eventDispatcher.addEventListener( type, listener, useCapture, priority, useWeakReference );
		}

		public function removeEventListener( type:String, listener:Function, useCapture:Boolean = false ):void
		{
			_eventDispatcher.removeEventListener( type, listener, useCapture );
		}

		public function dispatchEvent( event:Event ):Boolean
		{
			return _eventDispatcher.dispatchEvent( event );
		}

		public function hasEventListener( type:String ):Boolean
		{
			return _eventDispatcher.hasEventListener( type );
		}

		public function willTrigger( type:String ):Boolean
		{
			return _eventDispatcher.willTrigger( type );
		}

		/**
		 *  @private
		 *  Attempts to call getItemAt(), converting the property name into an int.
		 */
		override flash_proxy function getProperty( name:* ):*
		{
			if (name is QName)
				name = name.localName;

			var index:int = parseInt( name );
			if (index == -1)
			{
				throw new Error( "索引越界" );
			}
			else
			{
				return getAt( index );
			}
		}

		/**
		 *  @private
		 *  Attempts to call setItemAt(), converting the property name into an int.
		 */
		override flash_proxy function setProperty( name:*, value:* ):void
		{
			if (name is QName)
				name = name.localName;

			var index:int = parseInt( name );
			if (index == -1)
			{
				throw new Error( "索引越界" );
			}
			else
			{
				setAt( index, value );
			}
		}

		/**
		 *  @private
		 *  This is an internal function.
		 *  The VM will call this method for code like <code>"foo" in bar</code>
		 *  
		 *  @param name The property name that should be tested for existence.
		 */
		override flash_proxy function hasProperty( name:* ):Boolean
		{
			if (name is QName)
				name = name.localName;

			var index:int = parseInt( name );
			if (index == -1)
				return false;

			return index >= 0 && index < length;
		}

		/**
		 *  @private
		 */
		override flash_proxy function nextNameIndex( index:int ):int
		{
			return index < length ? index + 1 : 0;
		}

		/**
		 *  @private
		 */
		override flash_proxy function nextName( index:int ):String
		{
			return (index - 1).toString();
		}

		/**
		 *  @private
		 */
		override flash_proxy function nextValue( index:int ):*
		{
			return getAt( index - 1 );
		}

		/**
		 *  @private
		 *  Any methods that can't be found on this class shouldn't be called,
		 *  so return null
		 */
		override flash_proxy function callProperty( name:*, ... rest ):*
		{
			return null;
		}
	}
}
